<?php
/***********************************************************
 * 在线更新
 * @作者 pcfcms <1131680521@qq.com>
 * @主页 http://www.pcfcms.com
 * @时间 2021年01月01日
***********************************************************/
namespace app\admin\controller;
use think\facade\Db;
use think\facade\Session;
use think\facade\Request;
use think\facade\Cache;
class Upgrade extends Base
{
    public $popedom;
    function _initialize(){
        parent::_initialize();
        $ctl_act = strtolower(Request::controller().'/index');
        $this->popedom = appfile_popedom($ctl_act);
    }

    // 已更新列表
    public function index(){
        //验证查看权限
        if(!$this->popedom["list"]){
            return $this->Notice(config('params.auth_msg.list'),true,3,false);
        }
        $list = Db::name('upgrade')->order('ver desc')->select()->toArray();
        foreach ($list as $key => $value) {
           $list[$key]['add_time'] = pcftime($value['add_time']);
        }
        $assigndata['list'] = $list;
        $this->assign($assigndata);
        return $this->fetch();
    }

    // 检测是否有新版本
    public function check_version(){
        $server_dir = 'http://upgrade.pcfcms.com/public/update/up_log.txt';
        $local_dir = WWW_ROOT.'public/update/version.txt';
        // 获取版本记录文件
        $server = pcfcms_getfile($server_dir);
        if ($server === false) {
            $result= ['code'=>0,'msg'=>'服务器版本记录文件获取失败','data'=>''];
            return json($result);
        }
        // 最新版本
        $server = explode(",", $server);
        $last_version = end($server);
        // 本地版本
        $local = pcfcms_getfile($local_dir);
        if($local === false) {
            $result= ['code'=>0,'msg'=>'本地版本记录文件获取失败','data'=>''];
        }else {
            // 比较版本
            $data = ['last_version' =>$last_version];
            if (intval($last_version) > intval($local)) {
                $result= ['code'=>1,'msg'=>'服务器有新版本','data'=>$data];
            } else {
                $result= ['code'=>0,'msg'=>'已经是最新版本','data'=>$data];
            }
        }
        return json($result);
    }

    // 在线更新
    public function system_update(){

        if(Session::get('admin_info.role_id') > 0 && Session::get('admin_info.auth_role_info.online_update') == 0){
            $result = ['code' => 0, 'msg' => "管理员设置了不可以在线升级"];
            return json($result);
        }
        //验证权限
        if(!$this->popedom["update"]){
            if(config('params.auth_msg.test')){
                $result = ['code' => 0, 'msg' => config('params.auth_msg.pcfcms')];
                return json($result);
            }else{
                $result = ['code' => 0, 'msg' => config('params.auth_msg.other')];
                return json($result);                    
            }
        }
        $data = [];

        // 访问服务器判断有效期：
        // $res = $this->dataRequest('http://upgrade.pcfcms.com/upgrade/getlist',false,'post',$data);
        
        $res = true;
        // if ($res->data === false) {
        if ($res === false) {
            // 不在有效期
            $result = ['code'=>0,'msg'=>'已过服务有效期','data'=>''];
        } else {
            // 有效期内 开始更新
            $base_dir = WWW_ROOT.'public';
            // 服务器更新路径
            $update_res = 'http://upgrade.pcfcms.com/public/update/';
            // 本地更新路径
            $local_up_dir = $base_dir.'/update/';
            // 本地缓存路径
            $path = $base_dir.'/update/cache';
            // 没有就创建
            if(!is_dir($path)){
                mkdir(iconv("UTF-8","GBK",$path),0777,true);
            }
            // 设定缓存目录名称
            $cache_dir = $path.'/';
            // 初始化
            if(file_exists($cache_dir)) {
                $del_res = $this->deldir($cache_dir);
                if (empty($del_res)) {
                    $result = ['code'=>0,'msg'=>'在线升级初始化失败，请重试','data'=>''];
                    return json($result);
                }
            }
            // 服务器更新日志存放路径
            $server = pcfcms_getfile($update_res.'up_log.txt');
            if ($server === false) {
                $result = ['code'=>0,'msg'=>'服务器更新日志获取失败','data'=>''];
            }else{
                // 版本记录
                $server = explode(",", $server);
                $local = pcfcms_getfile($local_up_dir.'version.txt');
                if ($local === false) {
                    $result = ['code'=>0,'msg'=>'本地更新日志获取失败','data'=>''];
                } else {
                    // 循环比较是否需要下载 更新
                    foreach ($server as $key => $value) {    
                        if($local < $value){
                            // 获取更新信息
                            $up_info = pcfcms_getfile($update_res.$value.'/version.txt');
                            // 判断是否存在
                            if ($up_info === false){
                                $result = ['code'=>0,'msg'=>'服务器更新包不存在','data'=>''];
                            } else {
                                $up_info = json_decode($up_info);
                                // 下载文件
                                $back = $this->downloadFile($up_info->download,$cache_dir);
                                if(empty($back)){
                                    $this->deldir($cache_dir);
                                    $result = ['code'=>0,'msg'=>'升级程序包下载失败','data'=>''];
                                } else{
                                    // 创建解压目录
                                    if(!is_dir($cache_dir.$value)){
                                        mkdir(iconv("UTF-8", "GBK",$cache_dir.$value),0777,true);
                                    }
                                    // 下载成功 解压缩
                                    $zip_res = $this->deal_zip($back['save_path'],$cache_dir.$value);
                                    // 判断解压是否成功
                                    if ($zip_res){
                                        // 开始更新数据库和文件
                                        if(file_exists($cache_dir.$value.'/sql/')){
                                           $sql_res = $this->carry_sql($cache_dir.$value.'/sql/'); 
                                           if ($sql_res === false){
                                              $this->deldir($cache_dir);
                                              $result = ['code'=>0,'msg'=>'sql文件写入失败','data'=>''];
                                           }
                                        }
                                        // 文件合并 返回处理的文件数
                                        $file_up_res = $this->copy_merge($cache_dir.$value.'/program',WWW_ROOT);
                                        if (empty($file_up_res) || $file_up_res == 0){
                                            $this->deldir($cache_dir);
                                            $result = ['code'=>0,'msg'=>'文件移动合并失败','data'=>''];
                                        }else{
                                            // 更新完改写网站本地版号
                                            $write_res = file_put_contents($local_up_dir.'version.txt', $value);
                                            if (empty($write_res)) {
                                                $this->deldir($cache_dir);
                                                $result = ['code'=>0,'msg'=>'本地更新日志改写失败','data'=>''];
                                            }else{
                                                $this->deldir($cache_dir);
                                                $result = ['code'=>1,'msg'=>'在线升级已完成','data'=>''];
                                                $this->UpdateLog(['ver' => $value,'desc'=>$up_info->desc,'files'=>$file_up_res]);
                                            }
                                        }
                                    } else {
                                       $this->deldir($cache_dir);
                                       $result = ['code'=>0,'msg'=>'文件解压缩失败','data'=>''];
                                    }
                                }
                            }
                        }else{
                            $result = ['code'=>0,'msg'=>'本地已经是最新版','data'=>''];
                        }
                    }
                }
            }
        }
        return json($result);
    }

    // 下载文件地址
    private function downloadFile($fileUrl,$save_dir){
        if (trim($fileUrl) =='') {return false;}
        if (trim($save_dir) =='') {return false;}
        $filename = basename($fileUrl);
        //创建保存目录
        if (!file_exists($save_dir) && !mkdir($save_dir,0777,true)) {
            return false;
        }
        //开始下载
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $fileUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
        $content = curl_exec($ch);
        $status = curl_getinfo($ch);
        curl_close($ch);
        // 判断执行结果
        if ($status['http_code'] == 200) {
            $size = strlen($content);
            $fp2 = @fopen($save_dir.$filename,'a');
            fwrite($fp2, $content);
            fclose($fp2);
            unset($content,$fileUrl);
            $res = ['status'=>$status['http_code'],'file_name'=>$filename,'save_path'=>$save_dir.$filename];
        } else {
            $res = false;
        }
        return $res;
    }

    /**
     * 解压缩
     * @param $file 要解压的文件
     * @param $todir 要存放的目录
     */
    private function deal_zip($file,$todir){
        if (trim($file) ==''){return false;}
        if (trim($todir) ==''){return false;}
        $zip = new \ZipArchive;
        if ($zip->open($file) === TRUE) {
            $zip->extractTo($todir);
            $zip->close();
            $result = true;
        } else {
            $result = false;
        }
        return $result;
    }

    /**
     * 合并目录且只覆盖不一致的文件
     * @param $source 要合并的文件夹
     * @param $target 要合并的目的地
     */
    private function copy_merge($source,$target) {
        if (trim($source) ==''){return false;}
        if (trim($target) ==''){return false;}
        // 路径处理
        $source = preg_replace('#/\\\\#',DIRECTORY_SEPARATOR,$source);
        $target = preg_replace('#\/#',DIRECTORY_SEPARATOR,$target);
        $source = rtrim ($source,DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
        $target = rtrim ($target,DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
        // 记录处理了多少文件
        $count = 0;
        // 如果目标目录不存在，则创建。
        if (!is_dir($target)) {
            mkdir($target,0777,true);
            $count ++;
        }
        // 搜索目录下的所有文件
        foreach (glob($source.'*') as $filename) {
            if (is_dir($filename)) {
                // 如果是目录，递归合并子目录下的文件。
                $count += $this->copy_merge($filename,$target.basename($filename));
            } elseif (is_file($filename)) {
                // 如果是文件，判断当前文件与目标文件是否一样，不一样则拷贝覆盖。
                // 这里使用的是文件md5进行的一致性判断，可靠但性能低。
                if (!file_exists($target.basename($filename)) || md5(file_get_contents($filename)) != md5(file_get_contents($target.basename($filename)))){
                    copy($filename,$target.basename($filename));
                    $count ++;
                }
            }
        }
        return $count;
    }

    // 遍历执行sql文件
    private function carry_sql($dir){
        if (trim($dir) ==''){return false;}
        $sql_file_res = $this->scan_dir($dir);
        if (!empty($sql_file_res)) {
            $ext_sql = '.sql';
            foreach ($sql_file_res as $k => $v) {
                if (!empty(strstr($v,$ext_sql))) {
                    $sql_content = file_get_contents($dir.$v);
                    $sql_arr = explode(';',$sql_content);
                    //执行sql语句
                    foreach ($sql_arr as $vv) {
                        if (!empty($vv)){
                            try{
                                Db::execute($vv.';');
                            } catch (\Exception $e) {
                                continue;
                            }
                        }
                    }
                }
            }
        }
        return true;
    }

    // 遍历删除文件
    private function deldir($dir) {
        if (trim($dir) =='') {return false;}
        return delFile($dir);
    }

    /**
     * 遍历当前目录不包含下级目录
     * @param $dir 要遍历的目录
     * @param $file 要过滤的文件
     */
    private function scan_dir($dir,$file=''){
        if (trim($dir) ==''){return false;}
        $file_arr = scandir($dir);
        $new_arr = [];
        foreach($file_arr as $item){
            if($item!=".." && $item !="." && $item != $file){
                $new_arr[] = $item;
            }
        }
        return $new_arr;
    }

    // 升级记录 log 日志
    private function UpdateLog($data){
        $vaules = [
            'ver'=> $data['ver'],               
            'content'=> $data['desc'],            
            'add_time'=> getTime(),
            'files' => $data['files'],
        ];
        if($data){
           Db::name('upgrade')->save($vaules);
        }
    }

    // 外部请求
    private function dataRequest($url, $https = true, $method = 'get', $data = null){
        if (trim($url) == ''){return false;}
        //初始化curl
        $ch = curl_init($url);
        //字符串不直接输出，进行一个变量的存储
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        //https请求
        if ($https === true){
            //确保https请求能够请求成功
            curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
            curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false);
        }
        //post请求
        if ($method == 'post'){
            curl_setopt($ch,CURLOPT_POST,true);
            curl_setopt($ch,CURLOPT_POSTFIELDS,$data);
        }
        //发送请求
        $str = curl_exec($ch);
        $aStatus = curl_getinfo($ch);
        //关闭连接
        curl_close($ch);
        if(intval($aStatus["http_code"]) == 200){
            return json_decode($str);
        }else{
            return false;
        }
    }

}